using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Decav.Transactions
{
    /// <summary>
    /// Represents a target member of a data bind.
    /// </summary>
    public class BindingTarget
    {
        /// <summary>
        /// Initializes a new <see cref="BindingTarget"/>.
        /// </summary>
        /// <param name="target">The object to bind to.</param>
        /// <param name="member">The member on the <paramref name="target"/>
        /// to bind to.</param>
        /// <param name="updatedEvent">The event that signals that the value has been
        /// updated.</param>
        public BindingTarget(object target, MemberInfo member, EventInfo updatedEvent)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            if (member == null)
                throw new ArgumentNullException("member");
        

            _target = target;
            _member = member;
            _updatedEvent = updatedEvent;
        }

        /// <summary>
        /// Initializes a new <see cref="BindingTarget"/>.
        /// </summary>
        /// <param name="target">The object to bind to.</param>
        /// <param name="member">The member on the <paramref name="target"/>
        /// to bind to.</param>
        public BindingTarget(object target, MemberInfo member)
            : this(target, member, null)
        {
        }

        private object _target;
        private MemberInfo _member;
        private EventInfo _updatedEvent;

        /// <summary>
        /// Gets the target object of the bind.
        /// </summary>
        public object Target
        {
            get
            {
                return _target;
            }
        }

        /// <summary>
        /// Gets the member to set on the <see cref="Target"/>.
        /// </summary>
        public MemberInfo Member
        {
            get
            {
                return _member;
            }
        }

        /// <summary>
        /// Gets the optional event that signals that the value has been updated.
        /// </summary>
        public EventInfo UpdatedEvent
        {
            get
            {
                return _updatedEvent;
            }
        }

        /// <summary>
        /// Gets or sets the value of the <see cref="Member"/>.
        /// </summary>
        public object Value
        {
            get
            {
                PropertyInfo prop = Member as PropertyInfo;
                FieldInfo field = Member as FieldInfo;

                if (prop != null)
                {
                    return prop.GetValue(Target, null);
                }
                else if (field != null)
                {
                    return field.GetValue(Target);
                }
                else
                {
                    throw new InvalidOperationException("The DataBinder cannot bind a member that is not a property or field.");
                }
            }
            set
            {
                if (value == Value)
                    return; // Don't get caught in a stack overflow when trying to set the same value...

                PropertyInfo prop = Member as PropertyInfo;
                FieldInfo field = Member as FieldInfo;

                if (prop != null)
                {
                    prop.SetValue(Target, value, null);
                }
                else if (field != null)
                {
                    field.SetValue(Target, value);
                }
                else
                {
                    throw new InvalidOperationException("The DataBinder cannot bind a member that is not a property or field.");
                }
            }
        }
    }
}
